001 /*
002 * Copyright 2004-2005 Niclas Hedhman
003 * Copyright 2005 Stephen McConnell
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
014 * implied.
015 *
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 */
019
020 package net.dpml.transit;
021
022 import java.io.IOException;
023 import java.rmi.NoSuchObjectException;
024 import java.rmi.Remote;
025 import java.rmi.RemoteException;
026 import java.rmi.server.UnicastRemoteObject;
027 import java.net.PasswordAuthentication;
028 import java.net.URL;
029 import java.util.Properties;
030
031 import net.dpml.transit.link.ArtifactLinkManager;
032 import net.dpml.transit.link.LinkManager;
033 import net.dpml.transit.model.CacheModel;
034 import net.dpml.transit.model.TransitModel;
035 import net.dpml.transit.model.ProxyModel;
036 import net.dpml.transit.model.ProxyListener;
037 import net.dpml.transit.model.ProxyEvent;
038 import net.dpml.transit.model.RequestIdentifier;
039 import net.dpml.transit.model.DisposalListener;
040 import net.dpml.transit.model.DisposalEvent;
041 import net.dpml.transit.monitor.LoggingAdapter;
042
043 import net.dpml.lang.UnknownKeyException;
044 import net.dpml.util.Logger;
045
046
047 /**
048 * The initial context of the transit system.
049 *
050 * @author <a href="http://www.dpml.net">Digital Product Meta Library</a>
051 * @version 1.0.2
052 */
053 public final class SecuredTransitContext
054 {
055 //------------------------------------------------------------------
056 // static
057 //------------------------------------------------------------------
058
059 /**
060 * Creation of the transit context. If the transit context has already
061 * been established the method returns the singeton context otherwise a new
062 * context is created relative to the authoritve url and returned.
063 * @param model the active transit model
064 * @return the secured transit context
065 * @exception TransitException if an error occurs during context creation
066 * @exception NullArgumentException if the supplied configration model is null
067 * and an instance of this class has not been created already.
068 */
069 public static SecuredTransitContext create( TransitModel model )
070 throws TransitException, NullArgumentException
071 {
072 synchronized( SecuredTransitContext.class )
073 {
074 if( m_CONTEXT != null )
075 {
076 return m_CONTEXT;
077 }
078
079 if( null == model )
080 {
081 throw new NullArgumentException( "model" );
082 }
083
084 Logger logger = resolveLogger( model );
085 if( logger.isDebugEnabled() )
086 {
087 logger.debug( "creating transit context" );
088 }
089
090 try
091 {
092 m_CONTEXT = new SecuredTransitContext( model, logger );
093 }
094 catch( TransitException e )
095 {
096 throw e;
097 }
098 catch( Exception e )
099 {
100 String error = "Unable to establish the transit context.";
101 throw new TransitException( error, e );
102 }
103
104 return m_CONTEXT;
105 }
106 }
107
108 private static Logger resolveLogger( TransitModel model )
109 {
110 if( model instanceof DefaultTransitModel )
111 {
112 DefaultTransitModel m = (DefaultTransitModel) model;
113 return m.getLoggingChannel();
114 }
115 else
116 {
117 return new LoggingAdapter();
118 }
119 }
120
121 /**
122 * Return the singleton context.
123 * @return the secure context
124 */
125 public static SecuredTransitContext getInstance()
126 {
127 synchronized( SecuredTransitContext.class )
128 {
129 if( null == m_CONTEXT )
130 {
131 throw new IllegalStateException( "context" );
132 }
133 else
134 {
135 return m_CONTEXT;
136 }
137 }
138 }
139
140 //------------------------------------------------------------------
141 // state
142 //------------------------------------------------------------------
143
144 /**
145 * The configuration model.
146 */
147 private TransitModel m_model;
148
149 /**
150 * The cache handler.
151 */
152 private CacheHandler m_cacheHandler;
153
154 /**
155 * The LinkManager instance.
156 */
157 private LinkManager m_linkManager;
158
159 /**
160 * Logging channel.
161 */
162 private Logger m_logger;
163
164 private ProxyController m_proxyController;
165
166 private DisposalController m_disposalController;
167
168 //------------------------------------------------------------------
169 // constructors
170 //------------------------------------------------------------------
171 /**
172 * Creation of a new secured transit context.
173 * @param model the transit configuration model
174 * @param logger the assigned logging channel
175 * @exception IOException if an I/O error occurs
176 */
177 private SecuredTransitContext( TransitModel model, Logger logger ) throws IOException
178 {
179 m_model = model;
180 m_logger = logger;
181
182 CacheModel cacheModel = model.getCacheModel();
183 Logger cacheLogger = logger.getChildLogger( "cache" );
184 DefaultCacheHandler cache = new DefaultCacheHandler( cacheModel, cacheLogger );
185 m_cacheHandler = cache;
186 ProxyModel proxy = m_model.getProxyModel();
187 if( null != proxy )
188 {
189 synchronized( proxy )
190 {
191 setupProxy();
192 m_proxyController = new ProxyController();
193 proxy.addProxyListener( m_proxyController );
194 }
195 }
196 m_disposalController = new DisposalController();
197 model.addDisposalListener( m_disposalController );
198 }
199
200 //------------------------------------------------------------------
201 // SecuredTransitContext
202 //------------------------------------------------------------------
203
204 /**
205 * Return a layout object matching the supplied identifier.
206 * @param id the layout identifier
207 * @return the layout object
208 * @exception UnknownKeyException if the supplied layout id is unknown
209 * @exception IOException if an IO error occurs
210 */
211 public Layout getLayout( String id ) throws UnknownKeyException, IOException
212 {
213 LayoutRegistry registry = m_cacheHandler.getLayoutRegistry();
214 Layout layout = registry.getLayout( id );
215 if( null == layout )
216 {
217 throw new UnknownKeyException( id );
218 }
219 else
220 {
221 return layout;
222 }
223 }
224
225 /**
226 * Return the cache layout.
227 * @return the layout
228 */
229 public Layout getCacheLayout()
230 {
231 return getCacheHandler().getLayout();
232 }
233
234 /**
235 * Return the cache handler.
236 * @return the cache handler
237 */
238 public CacheHandler getCacheHandler()
239 {
240 return m_cacheHandler;
241 }
242
243 /**
244 * Return the link manager.
245 * @return the cache handler
246 */
247 public LinkManager getLinkManager()
248 {
249 return m_linkManager;
250 }
251
252 //------------------------------------------------------------------
253 // internals
254 //------------------------------------------------------------------
255
256 /**
257 * General setup.
258 * @exception RemoteException if a remote error occurs
259 */
260 protected synchronized void setupProxy() throws RemoteException
261 {
262 ProxyModel model = m_model.getProxyModel();
263 URL proxy = model.getHost();
264 if( null != proxy )
265 {
266 PasswordAuthentication auth = model.getAuthentication();
267 if( null != auth )
268 {
269 TransitAuthenticator ta = new TransitAuthenticatorImpl( auth );
270 RequestIdentifier id = model.getRequestIdentifier();
271 DelegatingAuthenticator da = DelegatingAuthenticator.getInstance();
272 da.addTransitAuthenticator( ta, id );
273 }
274
275 int port = proxy.getPort();
276 Properties system = System.getProperties();
277 system.put( "http.proxyHost", proxy );
278 system.put( "http.proxyPort", "" + port );
279 String[] excludes = model.getExcludes();
280 String path = toExcludesPath( excludes );
281 if( null != path )
282 {
283 system.put( "http.nonProxyHosts", path );
284 }
285 }
286 }
287
288 /**
289 * Initialization of any sub-systems following the establishment of the initial
290 * transit system. As a general principal any subsystems that cannot be established
291 * for technical reasons (security or permission restrictions, etc.) should log
292 * an appropriate message and fallback to the initial setup thereby ensuring that
293 * an operable transit system is available.
294 *
295 * @exception IOException if an io error occurs
296 */
297 protected void initialize() throws IOException
298 {
299 m_linkManager = new ArtifactLinkManager();
300 initializeCache();
301 }
302
303 /**
304 * Cache initialization.
305 *
306 * @exception IOException if an initialization error occurs
307 */
308 private void initializeCache() throws IOException
309 {
310 getCacheHandler().initialize();
311 }
312
313 /**
314 * Internal listener to the proxy model.
315 */
316 private class ProxyController extends UnicastRemoteObject implements ProxyListener
317 {
318 /**
319 * Listener creation.
320 * @exception RemoteException if a remote error occurs
321 */
322 public ProxyController() throws RemoteException
323 {
324 super();
325 }
326
327 /**
328 * Notify a listener of the change to Transit proxy settings.
329 * @param event the proxy change event
330 */
331 public void proxyChanged( ProxyEvent event )
332 {
333 try
334 {
335 setupProxy();
336 }
337 catch( RemoteException e )
338 {
339 final String error =
340 "Unexpected error while attrempting to set proxy settings.";
341 getLogger().error( error, e );
342 }
343 }
344 }
345
346 /**
347 * Internal listener to the proxy model.
348 */
349 private class DisposalController extends UnicastRemoteObject implements DisposalListener
350 {
351 /**
352 * Listener creation.
353 * @exception RemoteException if a remote error occurs
354 */
355 public DisposalController() throws RemoteException
356 {
357 super();
358 }
359
360 /**
361 * Notify a listener of transit model disposal.
362 * @param event the disposal event
363 */
364 public void notifyDisposal( DisposalEvent event )
365 {
366 Thread thread = new Terminator();
367 thread.start();
368 }
369 }
370
371 /**
372 * Internal model terminator.
373 */
374 private class Terminator extends Thread
375 {
376 Terminator()
377 {
378 }
379
380 /**
381 * Initiate model retraction from the RMI.
382 */
383 public void run()
384 {
385 m_logger.debug( "initiating transit runtime disposal" );
386 terminate( m_proxyController );
387 terminate( m_cacheHandler );
388 terminate( m_disposalController );
389 m_logger.debug( "transit runtime disposal complete" );
390 }
391
392 private void terminate( Object object )
393 {
394 if( object instanceof Disposable )
395 {
396 Disposable disposable = (Disposable) object;
397 disposable.dispose();
398 }
399 if( object instanceof Remote )
400 {
401 try
402 {
403 Remote remote = (Remote) object;
404 UnicastRemoteObject.unexportObject( remote, true );
405 }
406 catch( NoSuchObjectException e )
407 {
408 // ignore
409 }
410 catch( Throwable e )
411 {
412 final String error =
413 "Unexpected error encountered during transit runtime termination.";
414 m_logger.warn( error, e );
415 }
416 }
417 }
418 }
419
420 private Logger getLogger()
421 {
422 return m_logger;
423 }
424
425 //------------------------------------------------------------------
426 // static (utils)
427 //------------------------------------------------------------------
428
429 /**
430 * Resolve the list of host names to be assigned as non-proxied hosts. If proxy
431 * excludes are defined the string returned contains the host name (wilcards allowed)
432 * separated by the "|" character. If no proxy excludes are defined the value returned
433 * shall be null. The implementation reads the set of attribute names associated
434 * with a preferences node named "excludes". Each attribute name is appended to
435 * a single string where names are separated by the "|" character.
436 *
437 * @param names an array of named excludes
438 * @return a string containing a sequence of excluded hosts (possibly null)
439 */
440 private static String toExcludesPath( String[] names )
441 {
442 String spec = null;
443 for( int i=0; i < names.length; i++ )
444 {
445 String name = names[i];
446 if( null == spec )
447 {
448 spec = name;
449 }
450 else
451 {
452 spec = spec + "|" + name;
453 }
454 }
455 return spec;
456 }
457
458 /**
459 * The namespace string for transit related properties.
460 */
461 public static final String DOMAIN = "dpml.transit";
462
463 /**
464 * The singleton transit context.
465 */
466 private static SecuredTransitContext m_CONTEXT;
467 }